/*
 * inst_decode.c
 *
 *  Created on: Jan 21, 2014
 *      Author: Raviraj
 */

#include"includes.h"

extern int DONE;

void decode_instruction(unsigned short instruction)
{
	//unsigned short temp_inst;
	//temp_inst = instruction & 0170000;
	//printf("temp_inst = %o\n", temp_inst);

	// Type A instructions
	// Double operand
	// OPR src,dest
    // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
	// | B| opcode |  mode  |   src  |  mode  |  dest  |
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

	// Type B instructions
	// Double operand with atleast one operand as reg
	// OPR reg,src/dest
	// includes JSR instruction as well
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
	// | 0|    7   | opcode |   reg  |  mode  |src/dest|
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

	// Type C instructions
	// Single operand
	// OPR dest
	// includes SWAB and JMP instructions as well
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
	// | B|    0   |     opcode      |  mode  |   reg  |
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

	// Type D instructions
	// Branch instructions with offset embedded in instruction. No extra operands
	// Bxx loc
	// ***Caution - actual offsets need to be checked (whether they are 8 bits or 6 bits wide)
	//              This is unclear from manual and wiki.
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
	// | x|    0   | 0| opcode |        offset         |
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

	// Type E instructions
	// Condition code setting instructions such as CLC, SEN etc.
	// RTS and HALT
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
	// | 0|    0   |    0   |  2/0   |    0   | reg/0  |
	// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+


	/*if(optional_display == 'y' || optional_display == 'Y')
	{
		printf("\n");
		printf("\n  Instruction : %.6o", instruction);
	}*/

	if( (instruction >= 0010000 && instruction <= 0067777)
			|| (instruction >= 0110000 && instruction <= 0167777) ) {
		decode_inst_group_A(instruction);

	} else if( (instruction >= 0070000 && instruction <= 0077777)
			|| (instruction >= 0004000 && instruction <= 0004777) ) {
		decode_inst_group_B(instruction);

	} else if( (instruction >= 0005000 && instruction <= 0006777)
			|| (instruction >= 0105000 && instruction <= 0106777)
			|| (instruction >= 0000100 && instruction <= 0000177)
			|| (instruction >= 0000300 && instruction <= 0000377) ) {
		decode_inst_group_C(instruction);

	} else if( (instruction >= 0000400 && instruction <= 0003777)
			|| (instruction >= 0100000 && instruction <= 0103777) ) {
		decode_inst_group_D(instruction);

	} else if( (instruction >= 0000200 && instruction <= 0000277)
			|| (instruction >= 0000000 && instruction <= 0000007)) {
		decode_inst_group_E(instruction);

	} else {
		printf("\n  Error : Unknown instruction %.6o.",instruction);
		exit_on_error();
		//printf("\n");
	}
}


void decode_inst_group_A(unsigned short instruction)
{
	unsigned short src_operand_address, dest_operand_address;
	unsigned short src_operand_reg, dest_operand_reg;
	unsigned short src_mode, dest_mode;
	unsigned short temp_inst;

	src_mode = (instruction & 0007000) >> 9;
	src_operand_reg = (instruction & 0000700) >> 6;

	dest_mode = (instruction & 0000070) >> 3;
	dest_operand_reg = (instruction & 0000007);

	src_operand_reg = R0 + (src_operand_reg * 2);
	dest_operand_reg = R0 + (dest_operand_reg * 2);
	src_operand_address = get_operand_address(src_mode, src_operand_reg);
	dest_operand_address = get_operand_address(dest_mode, dest_operand_reg);

	if(check_boundary_error(src_operand_address))
	{
		printf("\n  Error : Attempt to fetch data from an odd address.");
		exit_on_error();
		//printf("\n");
	}

	if(check_boundary_error(dest_operand_address))
	{
		printf("\n  Error : Attempt to fetch data from an odd address.");
		exit_on_error();
		//printf("\n");
	}

	temp_inst = (instruction & 0170000);
	switch(temp_inst)
	{
	case 0010000 :
		func_MOV(src_operand_address, dest_operand_address);
		break;

	case 0020000 :
		func_CMP(src_operand_address, dest_operand_address);
		break;

	case 0030000 :
		func_BIT(src_operand_address, dest_operand_address);
		break;

	case 0040000 :
		func_BIC(src_operand_address, dest_operand_address);
		break;

	case 0050000 :
		func_BIS(src_operand_address, dest_operand_address);
		break;

	case 0060000 :
		func_ADD(src_operand_address, dest_operand_address);
		break;

	case 0160000 :
		func_SUB(src_operand_address, dest_operand_address);
		break;

	default:
		printf("\n  Error : Instruction with machine code %.6o is not supported.", instruction);
		exit_on_error();
		//printf("\n");
		break;
	}

}

void decode_inst_group_B(unsigned short instruction)
{
	unsigned short src_operand_address, dest_operand_address;
	unsigned short src_operand_reg, dest_operand_reg;
	unsigned short dest_mode;
	unsigned short temp_inst;

	unsigned short instruction_PC = get_Program_Counter() - 2;
	unsigned short target_PC;

	src_operand_reg = (instruction & 0000700) >> 6;

	dest_mode = (instruction & 0000070) >> 3;
	dest_operand_reg = (instruction & 0000007);

	src_operand_reg = R0 + (src_operand_reg * 2);
	dest_operand_reg = R0 + (dest_operand_reg * 2);
	src_operand_address = src_operand_reg;
	dest_operand_address = get_operand_address(dest_mode, dest_operand_reg);

	if(check_boundary_error(dest_operand_address))
	{
		printf("\n  Error : Attempt to fetch data from an odd address.");
		exit_on_error();
		//printf("\n");
	}


	temp_inst = (instruction & 0177000);
	switch(temp_inst)
	{
	case 0004000 :
		if(check_boundary_error(dest_operand_address))
		{
			printf("\n  Boundary error : Attempt to fetch an instruction from an odd address.");
			exit_on_error();
			//printf("\n");
		}
		else
			target_PC = func_JSR(src_operand_address, dest_operand_address);
		write_branch_traces("JSR", instruction_PC, target_PC, 1);
		break;

	default:
		printf("\n  Error : Instruction with machine code %.6o is not supported.", instruction);
		exit_on_error();
		//printf("\n");
		break;
	}


}

void decode_inst_group_C(unsigned short instruction)
{
	unsigned short dest_operand_address;
	unsigned short dest_operand_reg;
	unsigned short dest_mode;
	unsigned short temp_inst;
	unsigned short instruction_PC = get_Program_Counter() - 2;

	dest_mode = (instruction & 0000070) >> 3;
	dest_operand_reg = (instruction & 0000007);

	dest_operand_reg = R0 + (dest_operand_reg * 2);
	dest_operand_address = get_operand_address(dest_mode, dest_operand_reg);

	if(check_boundary_error(dest_operand_address))
	{
		printf("\n  Error : Attempt to fetch data from an odd address.");
		exit_on_error();
		//printf("\n");
	}

	temp_inst = (instruction & 0177700);
	switch(temp_inst)
	{
	case 0005000 :
		func_CLR(CLR,dest_operand_address);
		break;

	case 0005100 :
		func_COM(COM,dest_operand_address);
		break;

	case 0005200 :
		func_INC(INC,dest_operand_address);
		break;

	case 0005300 :
		func_DEC(DEC,dest_operand_address);
		break;

	case 0005400 :
		func_NEG(NEG,dest_operand_address);
		break;

	case 0005500 :
		func_ADC(dest_operand_address);
		break;

	case 0005600 :
		func_SBC(dest_operand_address);
		break;

	case 0005700 :
		func_TST(TST,dest_operand_address);
		break;

	case 0006000 :
		func_ROR(dest_operand_address);
		break;

	case 0006100 :
		func_ROL(dest_operand_address);
		break;

	case 0006200 :
		func_ASR(dest_operand_address);
		break;

	case 0006300 :
		func_ASL(dest_operand_address);
		break;

	case 0000100 :
	// unconditional JMP
		if(dest_mode == 0) {
			printf("\n  Error : Register mode is illegal in JMP instruction.");
			//printf("\n  Instruction is skipped");
			exit_on_error();
			//printf("\n");
		} else {
			if(check_boundary_error(dest_operand_address))
			{
				printf("\n  Boundary error : Attempt to fetch an instruction from an odd address.");
				exit_on_error();
				//printf("\n");
			}
			else
			{
				//dest_operand_address = read_from_memory(dest_operand_address,DATA);
				set_Program_Counter(dest_operand_address);
				write_branch_traces("JMP", instruction_PC, dest_operand_address, 1);
			}
		}
		break;

	case 0000300 :
		func_SWAB(dest_operand_address);
		break;

	default:
		printf("\n  Error : Instruction with machine code %.6o is not supported.", instruction);
		exit_on_error();
		//printf("\n");
		break;
	}


}

void decode_inst_group_D(unsigned short instruction)
{
	unsigned char offset;
	unsigned short temp_inst;
	unsigned short target_PC;
	unsigned short instruction_PC = get_Program_Counter() - 2;
	int branch_taken = 0;
	char * branch_type;

	offset = (unsigned char)(instruction & 0000377);
	if(offset > 0177) {
		// Backward/Upward jumps
		offset = ((~offset) << 1);
		target_PC = instruction_PC - offset;
	} else {
		// Forward/Downward jumps
		offset = (offset << 1);
		target_PC = instruction_PC + offset + 2;
	}

	temp_inst = (instruction & 0177400);
	switch(temp_inst)
	{
	case 0000400 : //BR
		// unconditional branch
		branch_taken = 1;
		branch_type = "BR";
		break;

	case 0001000 :
		branch_taken = func_BRANCH(BNE);
		branch_type = "BNE";
		break;

	case 0001400 :
		branch_taken = func_BRANCH(BEQ);
		branch_type = "BEQ";
		break;

	case 0002000 :
		branch_taken = func_BRANCH(BGE);
		branch_type = "BGE";
		break;

	case 0002400 :
		branch_taken = func_BRANCH(BLT);
		branch_type = "BLT";
		break;

	case 0003000 :
		branch_taken = func_BRANCH(BGT);
		branch_type = "BGT";
		break;

	case 0003400 :
		branch_taken = func_BRANCH(BLE);
		branch_type = "BLE";
		break;

	case 0100000 :
		branch_taken = func_BRANCH(BPL);
		branch_type = "BPL";
		break;

	case 0100400 :
		branch_taken = func_BRANCH(BMI);
		branch_type = "BMI";
		break;

	case 0101000 :
		branch_taken = func_BRANCH(BHI);
		branch_type = "BHI";
		break;

	case 0101400 :
		branch_taken = func_BRANCH(BLOS);
		branch_type = "BLOS";
		break;

	case 0102000 :
		branch_taken = func_BRANCH(BVC);
		branch_type = "BVC";
		break;

	case 0102400 :
		branch_taken = func_BRANCH(BVS);
		branch_type = "BVS";
		break;

	case 0103000 :
		branch_taken = func_BRANCH(BCC);
		branch_type = "BCC";
		break;

	case 0103400 :
		branch_taken = func_BRANCH(BCS);
		branch_type = "BCS";
		break;

	default:
		printf("\n  Error : Instruction with machine code %.6o is not supported.", instruction);
		exit_on_error();
		//printf("\n");
		break;
	}

	if(branch_taken)
		set_Program_Counter(target_PC);

	/*if(branch_taken) {
		printf("\n  Branch taken = YES");
		set_Program_Counter(target_PC);
		printf("\n  Target PC = %.6o", target_PC);
		printf("\n");
	}
	else
	{
		printf("\n  Branch taken = NO");
		printf("\n");
	}*/

	write_branch_traces(branch_type, instruction_PC, target_PC, branch_taken);

	// PC is already at next location so no else part is needed.
	// Just fetch instruction at the following PC if branch is not taken
}

void decode_inst_group_E(unsigned short instruction)
{
	unsigned short operand_reg;
	unsigned short ret_address;
	unsigned short instruction_PC = get_Program_Counter() - 2;

	operand_reg = (instruction & 0000007);

	operand_reg = R0 + (operand_reg * 2);

	switch(instruction)
	{
	case 0000200 : // RTS R0
	case 0000201 : // RTS R1
	case 0000202 : // RTS R2
	case 0000203 : // RTS R3
	case 0000204 : // RTS R4
	case 0000205 : // RTS R5
	case 0000206 : // RTS SP/R6
	case 0000207 : // RTS PC/R7
		ret_address = read_from_memory(operand_reg, DATA);
		if(check_boundary_error(ret_address))
		{
			printf("\n  Error : Attempt to fetch data from an odd address.");
			exit_on_error();
			//printf("\n");
		}

		func_RTS(operand_reg);

		//write_branch_traces("RTS", instruction_PC, read_from_memory(operand_reg, DATA), 1);
		write_branch_traces("RTS", instruction_PC, ret_address, 1);
		break;

	case 0000241 : // CLC
		clear_C_flag();
		break;

	case 0000242 : // CLV
		clear_V_flag();
		break;

	case 0000244 : // CLZ
		clear_Z_flag();
		break;

	case 0000250 : // CLN
		clear_N_flag();
		break;

	case 0000261 : // SEC
		set_C_flag();
		break;

	case 0000262 : // SEV
		set_V_flag();
		break;

	case 0000264 : // SEZ
		set_Z_flag();
		break;

	case 0000270 : // SEN
		set_N_flag();
		break;

	case 0000000 : // HALT
	case 0000005 : // RESET
		DONE = 1;
		break;

	default:
		printf("\n  Error : Instruction with machine code %.6o is not supported.", instruction);
		exit_on_error();
		//printf("\n");
		break;
	}
}

